/**
 * 	write on 2005-10-27 1:24:09.
 */
package org.nanhill.xmlro.sro;

import static org.nanhill.xmlro.sro.SroConstants.BOOLEAN;
import static org.nanhill.xmlro.sro.SroConstants.BOOLEAN_FALSE;
import static org.nanhill.xmlro.sro.SroConstants.BOOLEAN_TRUE;
import static org.nanhill.xmlro.sro.SroConstants.DATE;
import static org.nanhill.xmlro.sro.SroConstants.DOUBLE;
import static org.nanhill.xmlro.sro.SroConstants.INT;
import static org.nanhill.xmlro.sro.SroConstants.LIST;
import static org.nanhill.xmlro.sro.SroConstants.LONG;
import static org.nanhill.xmlro.sro.SroConstants.MAP;
import static org.nanhill.xmlro.sro.SroConstants.NULL;
import static org.nanhill.xmlro.sro.SroConstants.SIZE;
import static org.nanhill.xmlro.sro.SroConstants.SRO;
import static org.nanhill.xmlro.sro.SroConstants.STRING;

import java.io.Reader;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.nanhill.commons.lang.ReflectKit;
import org.nanhill.commons.lang.StringConstants;
import org.nanhill.xmlro.AbstractXmlroInput;
import org.nanhill.xmlro.Deserializer;
import org.nanhill.xmlro.DeserializerFactory;
import org.nanhill.xmlro.XmlroException;
import org.nanhill.xmlro.sro.model.SroDeserializerFactory;
import org.nanhill.xmlro.sro.obj.DifferCompTypeArray;

/**
 * This class is not thread safe, use <code>org.nanhill.xmlro.SroAgent</code>
 * only.
 * 
 * Object to xml parser.
 * 
 * @author alin
 * 
 */
class SroInput<E> extends AbstractXmlroInput<E> {
	private static final DeserializerFactory df = new SroDeserializerFactory();

	private static final int DEFAULT_LIST_SIZE = 32;

	private Stack<Object> wrapTrace = null;

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.nanhill.protocol.xmlro.input.Input#parseXml(java.io.Reader)
	 */
	public SroInput(Reader reader) {
		super(reader);
	}

	public SroInput() {
		super();
	}

	private void doWrap(Object wrap) {
		if (null != wrap) {
			isWrapped = true;
			wrapTrace = new Stack<Object>();
			wrapTrace.push(wrap);
		}
	}

	public void setWrap(E wrap) {
		this.doWrap(wrap);
	}

	public void wrapClz(Class<E> wrapClz) {
		this.doWrap(wrapClz);
	}

	private void assertWrapError(String message) throws XmlroException {
		throw new XmlroException("wrap trace error: " + message);
	}

	private void throwTagContentException(String message) throws XmlroException {
		throw new XmlroException("tag content error: " + message);
	}

	// private boolean isPojo(Class cl) {
	// if (ReflectKit.isMap(cl))
	// return false;
	// if (ReflectKit.isCollection(cl))
	// return false;
	// return isPojoIgnoreMapCollection(cl);
	// }

	private boolean notXmlroType(Class cl) {
		if (cl == String.class)
			return false;
		if (ReflectKit.isPrimitiveOrWrapType(cl))
			return false;
		// if (cl.isArray())
		// return false;
		if (java.util.Date.class.isAssignableFrom(cl))
			return false;
		if (Class.class == cl)
			return false;
		if (BigDecimal.class == cl)
			return false;
		return true;
	}

	private boolean canLogGenericType(Type genericType) {
		Class clz = genericType.getClass();
		// is pojo class
		if (Class.class == clz) {
			if (notXmlroType((Class) genericType)) {
				return true;
			}
		} else {
			return true;
		}
		// is parameterized type
		// if (ReflectKit.isDefaultParameterizedType(clz)) {
		// return genericType;
		// }
		return false;
	}

	private void logGenericType(Type genericType) {
		// Type logType = this.getLogType(genericType);
		if (this.canLogGenericType(genericType))
			wrapTrace.push(genericType);
	}

	@SuppressWarnings("unchecked")
	private void construct() throws XmlroException {
		// skip start document
		this.next();
		// assert the sro start tag
		assertStartTag(SRO);
		// move to the sro main object start tag
		this.next();
		this.result = (E) this.parse();
		assertEndTag(SRO);
	}

	public E echo() throws XmlroException {
		this.construct();
		return this.result;
	}

	public Object readList() throws XmlroException {
		// skip tag <list>
		next();
		// assert and skip tag <size>
		assertStartTag(SIZE);
		next();
		// init size with default value
		int size = DEFAULT_LIST_SIZE;
		if (!this.isEndTag(SIZE)) {
			try {
				size = Integer.parseInt(nodeText());
			} catch (NumberFormatException e) {
				e.printStackTrace();
				this
						.throwTagContentException("the text content between <size> and </size> must be a integer string.");
			}
			// skip tag content
			next();
		}
		// skip tag </size>
		next();
		Object rs = null;
		// is wrapped
		if (isWrapped) {
			Object thisWrap = this.wrapTrace.pop();
			Class thisWrapType = thisWrap.getClass();
			// read wrapped java.util.List
			if (ReflectKit.isDefaultParameterizedType(thisWrapType)) {
				ParameterizedType paraType = (ParameterizedType) thisWrap;
				if (ReflectKit.isCollection((Class) paraType.getRawType())) {
					// get collection component type(may be a collection also)
					Type compType = ReflectKit.getParameterizedTypes(paraType)[0];
					// get log type of this component type
					boolean canLog = this.canLogGenericType(compType);
					// init list
					List<Object> list = new ArrayList<Object>(size);
					while (false == this.canEndParseArray()) {
						if (canLog)
							wrapTrace.push(compType);
						list.add(parse());
					}
					rs = list;
				} else {
					assertWrapError("when read <List> by wrapped java.util.List, the class of this wrapped raw type must be java.util.List.class");
				}
				// TODO read wrapped generic array type
				// } else if
				// (ReflectKit.isDefaultGenericArrayType(thisWrapType)) {
				// GenericArrayType arrayType = (GenericArrayType) thisWrap;
				// // get generic array component type
				// Type compType = arrayType.getGenericComponentType();
				// // get log type of this component type
				// boolean canLog = this.canLogGenericType(compType);
				// Object array = Array.newInstance((Class)compType, size);
				// int i = 0;
				// while (false == this.canEndParseArray()) {
				// if (canLog)
				// wrapTrace.push(compType);
				// Array.set(array, i, parse());
				// i++;
				// }
				// rs = array;
				// read wrapped array class
			} else if (Class.class == thisWrapType) {
				Class wrapClz = (Class) thisWrap;
				if (Object.class == wrapClz || ReflectKit.isCollection(wrapClz)) {
					isWrapped = false;
					rs = readDefaultList(size);
					isWrapped = true;
				} else if (wrapClz.isArray()) {
					// get array component type(may be a array class also)
					Class compType = wrapClz.getComponentType();
					// get log type of this component type
					boolean canLog = this.canLogGenericType(compType);
					// init array
					// Object[] array = new Object[size];
					Object array = Array.newInstance(compType, size);
					int i = 0;
					while (false == this.canEndParseArray()) {
						if (canLog)
							wrapTrace.push(compType);
						Array.set(array, i, parse());
						i++;
					}
					rs = array;
				} else {
					assertWrapError("here must wrapped with a array class but "
							+ wrapClz);
				}
			} else if (DifferCompTypeArray.class == thisWrapType) {
				DifferCompTypeArray dcta = (DifferCompTypeArray) thisWrap;
				Type[] compTypes = dcta.getCompTypes();
				Object[] array = new Object[size];
				int i = 0;
				while (false == this.canEndParseArray()) {
					this.logGenericType(compTypes[i]);
					array[i] = parse();
					i++;
				}
				dcta.setCompValues(array);
				rs = dcta;
			} else {
				assertWrapError("when read <List>, the wrapped object must be generic type java.util.Collection or class array or object org.nanhill.xmlro.sro.obj.DifferCompTypeArray.");
			}
		} else {
			rs = readDefaultList(size);
		}
		// asert and skip tag </list>
		assertEndTag(LIST);
		next();
		return rs;
	}

	private List<Object> readDefaultList(int size) throws XmlroException {
		// init the list
		List<Object> list = new ArrayList<Object>(size);
		while (false == this.canEndParseArray()) {
			list.add(parse());
		}
		return list;
	}

	public Object readMap() throws XmlroException {
		Object rs = null;
		// skip tag <map>
		next();
		// is wrapped
		if (isWrapped) {
			// pop the wrap object
			Object thisWrap = wrapTrace.pop();
			Class thisWrapType = thisWrap.getClass();
			// read wrapped java.util.map
			if (ReflectKit.isDefaultParameterizedType(thisWrapType)) {
				ParameterizedType paraType = (ParameterizedType) thisWrap;
				if (ReflectKit.isMap((Class) paraType.getRawType())) {
					Type[] mapTypes = ReflectKit
							.getParameterizedTypes(paraType);
					Type keyType = mapTypes[0];
					Type valueType = mapTypes[1];
					Map<Object, Object> resultMap = new HashMap<Object, Object>();
					// do loop if the pointed tag isn't map end tag
					try {
						while (false == canEndParseMap()) {
							logGenericType(keyType);
							Object key = parse();
							logGenericType(valueType);
							Object value = parse();
							resultMap.put(key, value);
						}
					} catch (Exception e) {
						throw new XmlroException(e);
					}
					rs = resultMap;
				} else {
					assertWrapError("when parse wrapped java.util.map, the wrap trace raw type class must be java.util.Map.class");
				}
				// read other wrapped object(pojo class or pojo instance)
			} else {
				Class thisWrapClz = null;
				try {
					// init rs
					if (Class.class == thisWrapType) {
						thisWrapClz = (Class) thisWrap;
						if (Object.class == thisWrapClz
								|| ReflectKit.isMap(thisWrapClz)) {
							isWrapped = false;
							rs = readDefaultMap();
							isWrapped = true;
						} else {
							// if this wrap obj not a pojo instance but a pojo
							// class, instant it
							rs = thisWrapClz.newInstance();
							this.readWrappedMap(thisWrapClz, rs);
						}
						// if this wrap obj is a pojo instance
					} else {
						thisWrapClz = thisWrapType;
						rs = thisWrap;
						this.readWrappedMap(thisWrapClz, rs);
					}
				} catch (Exception e) {
					throw new XmlroException(e);
				}
			}
			// no wrapped
		} else {
			rs = readDefaultMap();
		}
		// assert and skip tag </map>
		assertMapEndTag();
		next();
		return rs;
	}

	private Map<Object, Object> readDefaultMap() throws XmlroException {
		Map<Object, Object> map = new HashMap<Object, Object>();
		while (false == canEndParseMap()) {
			Object key = parse();
			Object value = parse();
			map.put(key, value);
		}
		return map;
	}

	private void readWrappedMap(Class thisWrapClz, Object rs)
			throws SecurityException, IllegalArgumentException, XmlroException,
			NoSuchFieldException, IllegalAccessException {
		// do loop until the pointed tag is map end
		while (false == canEndParseMap()) {
			Object fieldName = parse();
			// field name is not java.lang.String, throw
			// exception
			if (fieldName.getClass() != String.class) {
				assertWrapError("The type of wrapped object's field name must be java.lang.String.class");
			}
			Field field = thisWrapClz.getDeclaredField((String) fieldName);
			// log wrap track
			logGenericType(field.getGenericType());
			// modify this wrap object
			field.setAccessible(true);
			field.set(rs, parse());
		}
	}

	public Date readDate() throws XmlroException {
		next();
		Date value = new Date(Long.parseLong(nodeText()));
		next();
		assertEndTag(DATE);
		next();
		return value;
	}

	public void readNull() throws XmlroException {
		next();
		assertEndTag(NULL);
		next();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.nanhill.protocol.xmlro.input.Input#readString(java.lang.String)
	 */
	public String readString() throws XmlroException {
		String value;
		this.next();
		if (this.isEndTag(STRING)) {
			value = StringConstants.EMPTY;
		} else {
			value = nodeText();
			this.next();
			this.assertEndTag(STRING);
		}
		this.next();
		return value;
	}

	public Integer readInteger() throws XmlroException {
		this.next();
		int value = Integer.valueOf(nodeText());
		this.next();
		this.assertEndTag(INT);
		this.next();
		return value;
	}

	public Long readLong() throws XmlroException {
		this.next();
		long value = Long.valueOf(nodeText());
		this.next();
		this.assertEndTag(LONG);
		this.next();
		return value;
	}

	public Boolean readBoolean() throws XmlroException {
		this.next();
		boolean value;
		String text = nodeText();
		if (BOOLEAN_TRUE.equals(text))
			value = true;
		else if (BOOLEAN_FALSE.equals(text))
			value = false;
		else
			throw new SroException("boolean value [" + text + "] incorrect!");
		this.next();
		this.assertEndTag(BOOLEAN);
		this.next();
		return value;
	}

	public Double readDouble() throws XmlroException {
		this.next();
		double value = Double.valueOf(nodeText());
		this.next();
		this.assertEndTag(DOUBLE);
		this.next();
		return value;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.nanhill.protocol.xmlro.Input#readBase64()
	 */
	public byte[] readBase64() throws XmlroException {
		next();
		byte[] value = this.decodeBase64(nodeText());
		next();
		next();
		return value;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.nanhill.protocol.xmlro.input.Input#assertText()
	 */
	// public void assertText() throws XmlroException {
	// if (TEXT != xpp.getEventType())
	// throw new XmlroException("not text node.");
	// }
	public void assertListEndTag() throws XmlroException {
		this.assertEndTag(LIST);
	}

	public void assertMapEndTag() throws XmlroException {
		this.assertEndTag(MAP);

	}


	public boolean canEndParseArray() throws XmlroException {
		if (isEndTag(LIST))
			return true;
		if (isEndDocument())
			return true;
		return false;
	}

	public boolean canEndParseMap() throws XmlroException {
		// if reached </map>
		if (isEndTag(MAP))
			return true;
		// if (isEndDocument())
		// return true;
		return false;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.nanhill.xmlro.AbstractXmlroInput#getDeserializer(java.lang.String)
	 */
	@Override
	protected Deserializer getDeserializer(String typeName) {
		return df.getDeserializer(typeName);
	}

}
